﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace AdvantIQ.SilverPool
{
    public partial class MainPage : UserControl
    {
        private Random rand = new Random(); // Random number generator for ball colors
        private bool knockMode = false; // Indicates if a ball is selected for knocking
        private int counter = 3;    // Used for ball numbering

        /// <summary>
        /// Designer generated code
        /// </summary>
        public MainPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initialisation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
        {
            InitObjects();
            StartTimer();
        }

        /// <summary>
        /// Attach event handlers and logical objects to all balls and holes
        /// </summary>
        private void InitObjects()
        {
            var UIballs = LayoutRoot.Children.OfType<Ellipse>().Where(
                    item => item.Name.StartsWith("Ball")).ToArray();

            for (int i = 0; i < UIballs.Length; i++)
            {
                var UIball = UIballs[i];
                var ball = new Ball((double)UIball.GetValue(Canvas.LeftProperty) + Constants.BallDiameter / 2,
                        (double)UIball.GetValue(Canvas.TopProperty) + Constants.BallDiameter / 2);
                UIball.Tag = ball;
                UIball.MouseLeftButtonDown += new MouseButtonEventHandler(Ball_MouseLeftButtonDown);
                UIball.MouseLeftButtonUp += new MouseButtonEventHandler(Ball_MouseLeftButtonUp);
                UIball.MouseMove += new MouseEventHandler(Ball_MouseMove);
            }

            var UIholes = LayoutRoot.Children.OfType<Ellipse>().Where(
                    item => item.Name.StartsWith("Hole")).ToArray();

            for (int i = 0; i < UIholes.Length; i++)
            {
                var UIhole = UIholes[i];
                var hole = new Hole((double)UIhole.GetValue(Canvas.LeftProperty) + Constants.HoleDiameter / 2,
                        (double)UIhole.GetValue(Canvas.TopProperty) + Constants.HoleDiameter / 2);
                UIhole.Tag = hole;
            }
        }

        /// <summary>
        /// Start the timer used to update the balls
        /// </summary>
        public void StartTimer()
        {
            System.Windows.Threading.DispatcherTimer myDispatcherTimer = new System.Windows.Threading.DispatcherTimer();
            myDispatcherTimer.Interval = TimeSpan.FromMilliseconds(10);
            myDispatcherTimer.Tick += new EventHandler(EachTick);
            myDispatcherTimer.Start();
        }

        /// <summary>
        /// Handles the movement of balls
        /// </summary>
        /// <param name="o"></param>
        /// <param name="sender"></param>
        public void EachTick(object o, EventArgs sender)
        {
            // Freeze if user is currently selecting a ball for knocking
            if (knockMode)
                return;

            var UIballs = _GetUIBalls();
            var balls = _GetBalls();

            var holes = LayoutRoot.Children.OfType<Ellipse>().Where(
                    item => item.Name.StartsWith("Hole")).Select(
                    item => (Hole)item.Tag).ToArray();

            // Check collisions
            for (int i = 0; i < balls.Length; i++)
            {
                balls[i].CheckCollisions(balls.Skip(i + 1), _GetWalls(), holes);
            }

            // Move
            for (int i = 0; i < balls.Length; i++)
            {
                var ball = balls[i];
                ball.Move();
                ball.ApplyFriction();
            }

            // Update UI objects
            foreach (var uiBall in UIballs)
            {
                var ball = (Ball)uiBall.Tag;

                if (ball.IsInHole)
                {
                    LayoutRoot.Children.Remove(uiBall);
                }
                else
                {
                    uiBall.SetValue(Canvas.LeftProperty, ball.X - Constants.BallDiameter / 2);
                    uiBall.SetValue(Canvas.TopProperty, ball.Y - Constants.BallDiameter / 2);
                }
            }
        }

        /// <summary>
        /// Select a ball for knocking
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Ball_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!knockMode)
            {
                var ball = (Ellipse)sender;
                var line = (Line)LayoutRoot.FindName("KnockLine");
                var point = e.GetPosition(LayoutRoot);
                line.X1 = point.X;
                line.Y1 = point.Y;
                line.X2 = (double)ball.GetValue(Canvas.LeftProperty) + ball.Width/2;
                line.Y2 = (double)ball.GetValue(Canvas.TopProperty) + ball.Height/2;
                line.Visibility = Visibility.Visible;

                ball.CaptureMouse();
                knockMode = true;
            }
        }

        /// <summary>
        /// Display knocking direction
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Ball_MouseMove(object sender, MouseEventArgs e)
        {
            if (knockMode)
            {
                var line = (Line)LayoutRoot.FindName("KnockLine");
                var point = e.GetPosition(LayoutRoot);
                line.X1 = point.X;
                line.Y1 = point.Y;
            }
        }

        /// <summary>
        /// Knock the selected ball
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Ball_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (knockMode)
            {
                var ball = (Ellipse)sender;
                var line = (Line)LayoutRoot.FindName("KnockLine");
                ball.ReleaseMouseCapture();
                knockMode = false;
                line.Visibility = Visibility.Collapsed;

                var dx = line.X2 - line.X1;
                var dy = line.Y2 - line.Y1;

                ((Ball)ball.Tag).Knock(dx * Constants.KnockStrength, dy * Constants.KnockStrength);
            }
        }

        /// <summary>
        /// Place new ball on the board
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Board_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var point = e.GetPosition(LayoutRoot);
            if (Ball.IsInsertable(point.X, point.Y, _GetBalls(), _GetWalls()))
            {
                var uiBall = new Ellipse();
                var ball = new Ball(point.X, point.Y);
                var rgb = new byte[3];

                uiBall.SetValue(Canvas.LeftProperty, ball.X - Constants.BallDiameter / 2);
                uiBall.SetValue(Canvas.TopProperty, ball.Y - Constants.BallDiameter / 2);
                uiBall.SetValue(Canvas.ZIndexProperty, 5);
                uiBall.Tag = ball;
                uiBall.Width = Constants.BallDiameter;
                uiBall.Height = Constants.BallDiameter;

                uiBall.MouseLeftButtonDown += new MouseButtonEventHandler(Ball_MouseLeftButtonDown);
                uiBall.MouseLeftButtonUp += new MouseButtonEventHandler(Ball_MouseLeftButtonUp);
                uiBall.MouseMove += new MouseEventHandler(Ball_MouseMove);

                rand.NextBytes(rgb);
                uiBall.Fill = new RadialGradientBrush(Colors.White, Color.FromArgb(255, rgb[0], rgb[1], rgb[2]));
                uiBall.Name = "Ball" + (counter++).ToString();

                LayoutRoot.Children.Add(uiBall);
            }
        }

        /// <summary>
        /// Gets ball UI elements
        /// </summary>
        /// <returns></returns>
        private Ellipse[] _GetUIBalls()
        {
            return LayoutRoot.Children.OfType<Ellipse>().Where(
                    item => item.Name.StartsWith("Ball")).ToArray();
        }

        /// <summary>
        /// Gets ball logical elements
        /// </summary>
        /// <returns></returns>
        private Ball[] _GetBalls()
        {
            return _GetUIBalls().Select(item => (Ball)item.Tag).ToArray();
        }

        /// <summary>
        /// Gets walls coordinates
        /// </summary>
        /// <returns></returns>
        private Rect _GetWalls()
        {
            var board = (Rectangle)LayoutRoot.FindName("Board");
            return new Rect(Constants.FrameThickness, Constants.FrameThickness,
                    board.Width, board.Height);
        }
    }
}
